package top.wshape1.shiziapi.gateway.filter;

import com.alibaba.fastjson2.JSON;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import top.wshape1.shiziapi.common.constant.AuthConstant;
import top.wshape1.shiziapi.common.constant.BizCodeEnum;
import top.wshape1.shiziapi.common.constant.RedisConstant;
import top.wshape1.shiziapi.common.domain.CommonUser;
import top.wshape1.shiziapi.common.domain.to.UserTokenTo;
import top.wshape1.shiziapi.common.utils.JwtUtil;
import top.wshape1.shiziapi.common.utils.R;
import top.wshape1.shiziapi.gateway.config.ShiZiApiConfig;
import top.wshape1.shiziapi.gateway.utils.ResponseUtil;

/**
 * @author Wshape1
 * @version 1.0
 * @description 校验Token，顺序优于GatewayGlobalFilter
 * @since 2023-08-24
 */

@Component
@Slf4j
public class JwtTokenFilter implements WebFilter {

    @Resource
    private ShiZiApiConfig shiZiApiConfig;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private WebClient webClient;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        log.info("JwtTokenFilter 我来了");
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        String header = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        if (header == null) {
            // 可能是websocket连接，通过链接参数
            String token = request.getQueryParams().getFirst(HttpHeaders.AUTHORIZATION);
            if (token != null)
                header = "Bearer " + token;
        }
        if (header == null)
            return chain.filter(exchange);
        else if (!header.startsWith("Bearer ")) {
            R<Void> r = R.instance(BizCodeEnum.NOT_FOUND_TOKEN.code(), BizCodeEnum.NOT_FOUND_TOKEN.msg());
            log.error("token no bearer resp401");
            return ResponseUtil.writeResponseWith401(response, r);
        } else {
            String token = header.replace("Bearer ", "");
            //验证token是否正确

            CommonUser user;
            try {
                Claims claims = JwtUtil.parseJWT(token, shiZiApiConfig.SIGN_KEY);
                user = JSON.parseObject(claims.getSubject(), CommonUser.class);

                // 查询Redis是否存在，否则为非法
                Object o = redisTemplate.opsForValue().get(RedisConstant.USER_JWT_KEY + user.getId());
                if (!token.equals(o)) {
                    R<Void> r = R.instance(BizCodeEnum.ILLEGAL_TOKEN.code(), BizCodeEnum.ILLEGAL_TOKEN.msg());
                    log.warn("illegal token  Resp401");
                    return ResponseUtil.writeResponseWith401(response, r);
                }
            } catch (ExpiredJwtException ejw) {
                // Token过期
                Claims claims = ejw.getClaims();
                user = JSON.parseObject(claims.getSubject(), CommonUser.class);
                //查询token是否在redis
                Object o = redisTemplate.opsForValue().get(RedisConstant.USER_JWT_KEY + user.getId());
                if (token.equals(o)) {
                    // 换新token
                    return webClient
                            .post()
                            .uri("lb://shiziapi-uaa/token/renew?id=" + user.getId())
                            .bodyValue(token)
                            .retrieve()
                            .bodyToMono(new ParameterizedTypeReference<R<UserTokenTo>>() {
                            })
                            .flatMap(r -> {
                                        if (r.getCode() == 0) {
                                            UserTokenTo userTokenTo = r.getData();
                                            // 放入user给SecurityContextRepository获取
                                            // 还有AuthGlobalFilter获取

                                            // 设置新的请求头，防止再次被判断token过期问题
                                            ServerHttpRequest build = request
                                                    .mutate()
                                                    .header(HttpHeaders.AUTHORIZATION, "Bearer " + userTokenTo.getNewToken())
                                                    .build();
                                            response.getHeaders().add(AuthConstant.HEADER_NEW_TOKEN_NAME, userTokenTo.getNewToken());
                                            exchange.getAttributes().put("user", userTokenTo.getUser());
                                            log.warn("webClient flatMap");
                                            return chain.filter(exchange.mutate().request(build).build());
                                        } else {
                                            log.warn("webClient Resp401");
                                            return ResponseUtil.writeResponseWith401(response, r);
                                        }
                                    }
                            );
                } else {
                    // token不在redis，真正过期
                    R<Void> r = R.instance(BizCodeEnum.EXPIRED_TOKEN.code(), BizCodeEnum.EXPIRED_TOKEN.msg());
                    log.warn("token real expired Resp401");
                    return ResponseUtil.writeResponseWith401(response, r);
                }
            } catch (Exception e) {
                R<Void> r = R.instance(BizCodeEnum.ILLEGAL_TOKEN.code(), BizCodeEnum.ILLEGAL_TOKEN.msg());
                log.error("JwtTokenFilter token err: {}\nToken: {}", e.getMessage(), token);
                return ResponseUtil.writeResponseWith403(response, r);
            }
            // 放入user给SecurityContextRepository获取
            // 还有AuthGlobalFilter获取
            exchange.getAttributes().put("user", user);

            return chain.filter(exchange);
        }
    }
}
